package mirkofrancuski.battleship.game;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Random;

import android.os.Parcel;
import android.os.Parcelable;

public class Board implements Parcelable {
	private Cell[][] board;

	private LinkedList<Cell> unplacedShips;

	private HashMap<Integer, Cell> shipLocations;

	private Random randomGenerator;

	private LinkedList<Ship> placedShips;

	public static final int BOARD_SIZE = 10;

	public static final Cell[] ships = { Cell.AIRCRAFT_CARRIER,
			Cell.BATTLESHIP, Cell.SUBMARINE, Cell.CRUISER, Cell.DESTROYER };

	public enum Direction {
		HORIZONTAL, VERTICAL
	};

	public Board() {
		board = new Cell[BOARD_SIZE][BOARD_SIZE];

		Arrays.fill(board[0], Cell.EMPTY);
		for (int i = 1; i < BOARD_SIZE; i++)
			board[i] = Arrays.copyOf(board[0], BOARD_SIZE);

		unplacedShips = new LinkedList<Cell>();
		unplacedShips.addAll(Arrays.asList(ships));
		placedShips = new LinkedList<Ship>();

		shipLocations = new HashMap<Integer, Cell>(17);

		randomGenerator = new Random();

	}

	private Board(Parcel in) {
		this();
		readFromParcel(in);
	}

	public static final Parcelable.Creator<Board> CREATOR = new Parcelable.Creator<Board>() {
		public Board createFromParcel(Parcel in) {
			return new Board(in);
		}

		public Board[] newArray(int size) {
			return new Board[size];
		}
	};

	@Override
	public int describeContents() {
		return 0;
	}

	@Override
	public void writeToParcel(Parcel dest, int flags) {
		for (int i = 0; i < BOARD_SIZE; i++)
			dest.writeArray(board[i]);

		dest.writeList(unplacedShips);
		dest.writeList(placedShips);
		dest.writeMap(shipLocations);
	}

	public void readFromParcel(Parcel in) {
		for (int i = 0; i < BOARD_SIZE; i++) {
			Object[] array = in.readArray(null);
			if (array == null)
				return;
			for (int j = 0; j < BOARD_SIZE; j++)
				board[i][j] = (Cell) array[j];

		}

		unplacedShips.clear();
		placedShips.clear();
		shipLocations.clear();
		in.readList(unplacedShips, null);
		in.readList(placedShips, null);
		in.readMap(shipLocations, null);

	}

	public void clear() {
		Arrays.fill(board[0], Cell.EMPTY);
		for (int i = 1; i < BOARD_SIZE; i++)
			board[i] = Arrays.copyOf(board[0], BOARD_SIZE);

		shipLocations.clear();
		unplacedShips.clear();
		unplacedShips.addAll(Arrays.asList(ships));
		placedShips.clear();
	}

	public void setBoard(int[][] matrix) {
		placedShips.clear();
		unplacedShips.clear();
		shipLocations.clear();

		unplacedShips.addAll(Arrays.asList(ships));

		Coordinates coord = new Coordinates();
		for (int i = 0; i < BOARD_SIZE; i++) {
			coord.horizontal = i;
			for (int j = 0; j < BOARD_SIZE; j++) {
				coord.vertical = j;
				board[i][j] = Cell.getCell(matrix[i][j]);
			}
		}

		for (int i = 0; i < BOARD_SIZE; i++) {
			coord.horizontal = i;
			for (int j = 0; j < BOARD_SIZE; j++) {
				coord.vertical = j;
				if (board[i][j] != Cell.MISS && board[i][j] != Cell.HIT
						&& board[i][j] != Cell.EMPTY) {
					unplacedShips.remove(board[i][j]);
					shipLocations.put(coord.hashCode(), board[i][j]);
					// find the ships
					if (selectPlacedShip(board[i][j]) == null) {
						Direction direction = getShipDirection(coord,
								board[i][j]);
						Coordinates begining = getShipBegining(coord,
								direction, board[i][j]);
						placedShips.add(new Ship(board[i][j], begining,
								direction));
					}
				}
			}
		}
	}

	public Cell takeHit(Coordinates coord) {
		if (board[coord.horizontal][coord.vertical] != Cell.EMPTY) {
			shipLocations.remove(coord.hashCode());
			Cell returnCell = Cell.HIT;
			if (!shipLocations.values().contains(
					board[coord.horizontal][coord.vertical]))
				// returns which ship is sunk
				returnCell = board[coord.horizontal][coord.vertical];

			board[coord.horizontal][coord.vertical] = Cell.HIT;
			return returnCell;

		}
		board[coord.horizontal][coord.vertical] = Cell.MISS;
		return Cell.MISS;
	}

	public boolean isInBounds(Coordinates coord) {
		return coord.horizontal < Board.BOARD_SIZE && coord.horizontal > -1
				&& coord.vertical < Board.BOARD_SIZE && coord.vertical > -1;
	}

	public boolean isEmpty(Coordinates coord) {
		return isInBounds(coord)
				&& board[coord.horizontal][coord.vertical] == Cell.EMPTY;
	}

	public boolean isHit(Coordinates coord) {
		return isInBounds(coord)
				&& board[coord.horizontal][coord.vertical] == Cell.HIT;
	}

	public boolean isMiss(Coordinates coord) {
		return isInBounds(coord)
				&& board[coord.horizontal][coord.vertical] == Cell.MISS;
	}

	public Cell getCell(Coordinates coord) {
		return board[coord.horizontal][coord.vertical];
	}

	public Cell getCell(int horizontal, int vertical) {
		return board[horizontal][vertical];
	}

	public void updateResult(Coordinates coord, Cell cell) {
		board[coord.horizontal][coord.vertical] = cell;
	}

	public void updateResult(Ship ship) {
		unplacedShips.remove(ship.shipCell);

		int x = ship.direction == Direction.HORIZONTAL ? 1 : 0;
		int y = ship.direction == Direction.VERTICAL ? 1 : 0;

		Coordinates coord = new Coordinates(ship.start);
		for (int k = 0; k < ship.getSize(); k++) {

			board[coord.horizontal][coord.vertical] = ship.shipCell;

			shipLocations.put(coord.hashCode(), ship.shipCell);

			coord.horizontal += y;
			coord.vertical += x;
		}

		placedShips.add(ship);

	}

	private boolean setDestroyed(Coordinates lastShot, Cell cell) {
		int shipSize = cell.getSize();
		int posibleWays = 0;
		int x = 0, y = 0;
		int p = 0, q = 0;
		Coordinates startCoord = null;

		for (int d = 0; d < 2; d++) {

			x = d == 0 ? 1 : 0;
			y = d == 1 ? 1 : 0;

			Coordinates coord = new Coordinates();
			for (int i = shipSize - 1; i >= 0; i--) {
				coord.horizontal = lastShot.horizontal - y * i;
				coord.vertical = lastShot.vertical - x * i;

				boolean sunken = true;
				for (int j = 0; j < shipSize && sunken; j++) {
					if (!coord.equals(lastShot))
						sunken = isHit(coord);
					coord.horizontal += y;
					coord.vertical += x;
				}
				if (!sunken)
					continue;

				posibleWays++;
				p = x;
				q = y;
				startCoord = new Coordinates(lastShot.horizontal - y * i,
						lastShot.vertical - x * i);
			}
		}

		if (posibleWays != 1) {
			if (!unplacedShips.contains(cell))
				unplacedShips.add(cell);
			board[lastShot.horizontal][lastShot.vertical] = Cell.HIT;
			return false;
		} else {
			for (int k = 0; k < shipSize; k++) {
				int i = startCoord.horizontal + q * k;
				int j = startCoord.vertical + p * k;
				board[i][j] = cell;
			}
			if (unplacedShips.contains(cell))
				unplacedShips.remove(cell);
			if (!unplacedShips.isEmpty()) {
				for (int i = 0; i < BOARD_SIZE; i++)
					for (int j = 0; j < BOARD_SIZE; j++) {
						Coordinates coord = new Coordinates(i, j);
						if (isHit(coord)) {
							setDestroyed(coord, unplacedShips.peek());
							if (unplacedShips.isEmpty())
								return true;
						}
					}
			}
			return true;
		}
	}

	public boolean areAllShipsSunk() {
		return shipLocations.isEmpty();
	}

	public void randomPlacement() {
		int direction;

		for (int i = 0; i < ships.length; i++) {
			Coordinates coord = new Coordinates();
			coord.horizontal = randomGenerator.nextInt(BOARD_SIZE);
			coord.vertical = randomGenerator.nextInt(BOARD_SIZE);
			direction = randomGenerator.nextInt(2);

			Ship ship = new Ship(ships[i], coord, Direction.values()[direction]);

			if (!placeShip(ship))
				i--;
		}
	}

	public Ship selectPlacedShip(Cell shipCell) {
		Iterator<Ship> it = placedShips.iterator();

		while (it.hasNext()) {
			Ship ship = it.next();
			if (ship.shipCell.equals(shipCell))
				return ship;
		}
		return null;

	}

	public boolean placeShip(Ship ship) {

		if (!unplacedShips.contains(ship.shipCell))
			return false;

		if (!isPlaceable(ship.start, ship.direction, ship.shipCell))
			return false;

		Coordinates coord = new Coordinates(ship.start);

		int i = ship.direction == Direction.VERTICAL ? 1 : 0;
		int j = ship.direction == Direction.HORIZONTAL ? 1 : 0;

		for (int k = 0; k < ship.getSize(); k++) {
			board[coord.horizontal][coord.vertical] = ship.shipCell;
			shipLocations.put(coord.hashCode(), ship.shipCell);
			coord.horizontal += i;
			coord.vertical += j;
		}
		unplacedShips.remove(ship.shipCell);
		placedShips.add(ship);
		return true;
	}

	public boolean isPlaceable(Coordinates coord, Direction direction, Cell ship) {
		int stepVertical = direction == Direction.HORIZONTAL ? 1 : 0;
		int stepHorizontal = direction == Direction.VERTICAL ? 1 : 0;

		if (!isInBounds(coord))
			return false;

		for (int k = 0, i = 0, j = 0; k < ship.getSize(); k++, i += stepHorizontal, j += stepVertical) {
			if (coord.horizontal + i == BOARD_SIZE
					|| coord.vertical + j == BOARD_SIZE
					|| board[coord.horizontal + i][coord.vertical + j] != Cell.EMPTY) {
				return false;
			}
		}
		return true;
	}

	public Coordinates getShipBegining(Coordinates coord, Direction direction,
			Cell shipCell) {

		Ship ship = selectPlacedShip(shipCell);
		if (ship != null)
			return ship.start;

		Coordinates oldBegin = new Coordinates(coord);
		boolean beginFound = false;
		int i = direction == Direction.VERTICAL ? 1 : 0;
		int j = direction == Direction.HORIZONTAL ? 1 : 0;
		while (!beginFound) {
			oldBegin.horizontal -= i;
			oldBegin.vertical -= j;
			beginFound = !isInBounds(oldBegin)
					|| board[oldBegin.horizontal][oldBegin.vertical] != shipCell;
		}
		oldBegin.horizontal += i;
		oldBegin.vertical += j;
		return oldBegin;
	}

	public Direction getShipDirection(Coordinates coord, Cell shipCell) {

		Ship ship = selectPlacedShip(shipCell);
		if (ship != null)
			return ship.direction;

		Coordinates coordUp = new Coordinates(coord);
		coordUp.horizontal++;
		Coordinates coordDown = new Coordinates(coord);
		coordDown.horizontal--;

		Direction direction = Direction.HORIZONTAL;
		if (isInBounds(coordUp) && getCell(coordUp) == shipCell
				|| isInBounds(coordDown) && getCell(coordDown) == shipCell)
			direction = Direction.VERTICAL;

		return direction;
	}

	public void pickUpShip(Ship ship) {
		Coordinates oldBegin = new Coordinates(ship.start);

		int i = ship.direction == Direction.VERTICAL ? 1 : 0;
		int j = ship.direction == Direction.HORIZONTAL ? 1 : 0;
		for (int k = 0; k < ship.getSize(); k++) {
			shipLocations.remove(oldBegin.hashCode());
			board[oldBegin.horizontal][oldBegin.vertical] = Cell.EMPTY;
			oldBegin.horizontal += i;
			oldBegin.vertical += j;
		}
		unplacedShips.add(ship.shipCell);
		placedShips.remove(selectPlacedShip(ship.shipCell));
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();

		for (int i = -1; i < BOARD_SIZE; i++) {
			for (int j = -1; j < BOARD_SIZE; j++) {
				if (i == -1) {
					sb.append(j == -1 ? " " : j);
					sb.append(" ");
					continue;
				}
				if (j == -1) {
					sb.append(i);
					sb.append(" ");
					continue;
				}
				sb.append(board[i][j].name().charAt(0));
				sb.append(" ");
			}
			sb.append('\n');
		}
		sb.append('\n');
		return sb.toString();
	}
	
	public boolean isSunk(Cell cell){
		if(unplacedShips.isEmpty()) { //all placed => myBoard
			return !shipLocations.values().contains(cell);
		}
		
		return shipLocations.values().contains(cell);
	}

}
